package be.artisjaap.htmlparser.core;
 

import java.io.InputStream;
import java.io.StringWriter;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;

import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

public class SiteParser {

    public void parse(InputStream xml, ISiteMapper sitemapper) {
        Document xdoc = convertToDoc(xml);
        parseToObject((Node) xdoc, sitemapper);

    }

    private void parseToObject(Node doc, Object sitemapper) {
        // Execute all methods that have the HtmlMapper annotation
        try {
            for (Field f : sitemapper.getClass().getDeclaredFields()) {
                if (f.isAnnotationPresent(HtmlMapper.class)) {
                    HtmlMapper mapper = f.getAnnotation(HtmlMapper.class);
                    // System.out.println("Path to find: " + mapper.xpath());

                    String setMethod;
                    Class<?> param = f.getType();

                    if (hasInterface(f.getType(), "java.util.Collection")) {
                        // System.out.println(" --> complex type found");

                        NodeList nodes = findXpath(mapper.xpath(), doc);
                        // System.out.println("  --> Nodes found: " +
                        // nodes.getLength());
                        setMethod = "add" + f.getName().substring(0, 1).toUpperCase() + f.getName().substring(1);
                        // System.out.println(" --> method: " + setMethod);

                        Type t = f.getGenericType();

                        if (t instanceof ParameterizedType) {
                            ParameterizedType type = (ParameterizedType) t;
                            Type[] typeArguments = type.getActualTypeArguments();
                            if (typeArguments.length == 1) {
                                Class<?> typeArgClass = (Class<?>) typeArguments[0];
                                param = typeArgClass;
                                Method m = sitemapper.getClass().getMethod(setMethod, typeArgClass);

                                for (int i = 0; i < nodes.getLength(); i++) {
                                    if (param.getName().equals("java.lang.String")) {
                                        String content = "";
                                        if (mapper.outputType() == HtmlMapper.OUTPUT_XML)
                                            content = xmlToString(nodes.item(i));
                                        else
                                            content = nodes.item(i).getTextContent().trim();
                                        Object arglist[] = new Object[1];
                                        arglist[0] = content;
                                        m.invoke(sitemapper, arglist);
                                    }
                                    else {
                                        Object newSiteMapper = param.newInstance();
                                        Object arglist[] = new Object[1];
                                        arglist[0] = newSiteMapper;
                                        m.invoke(sitemapper, arglist);

                                        parseToObject(nodes.item(i).cloneNode(true), newSiteMapper);
                                    }

                                }

                            }
                        }

                    }
                    else {
                        // System.out.println(" --> simple type found");
                        setMethod = "set" + f.getName().substring(0, 1).toUpperCase() + f.getName().substring(1);
                        // System.out.println(" --> method: " + setMethod);

                        NodeList nodes = findXpath(mapper.xpath(), doc);
                        // System.out.println("  --> Nodes found: " +
                        // nodes.getLength());

                        if (nodes.getLength() > 0) {
                            Method m = sitemapper.getClass().getMethod(setMethod, param);

                            String content = "";
                            if (mapper.outputType() == HtmlMapper.OUTPUT_XML)
                                content = xmlToString(nodes.item(0));
                            else
                                content = nodes.item(0).getTextContent().trim();
                            Object arglist[] = new Object[1];
                            arglist[0] = content;
                            m.invoke(sitemapper, arglist);

                        }

                    }

                }
            }
        }
        catch (Exception e) {
            System.out.println(e.getMessage());
        }
    }

    private NodeList findXpath(String xpathString, Node doc) {

        XPathFactory factory = XPathFactory.newInstance();
        XPath xpath = factory.newXPath();
        XPathExpression expr;
        try {
            if (xpathString != null && !xpathString.equals("")) {
                expr = xpath.compile(xpathString);

                Object result = expr.evaluate(doc, XPathConstants.NODESET);

                NodeList nodes = (NodeList) result;
                return nodes;
            }
            else {
                return (NodeList) doc;

            }

        }
        catch (XPathExpressionException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        catch (IllegalArgumentException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

        return null;
    }

    private boolean hasInterface(Class<?> clazz, String interfacePath) {
        for (Class<?> c : clazz.getInterfaces()) {
            if (c.getCanonicalName().equals(interfacePath)) return true;
        }
        return false;
    }

    public static String xmlToString(Node node) {
        try {
            Source source = new DOMSource(node);
            StringWriter stringWriter = new StringWriter();
            Result result = new StreamResult(stringWriter);
            TransformerFactory factory = TransformerFactory.newInstance();
            Transformer transformer = factory.newTransformer();
            transformer.transform(source, result);
            return stringWriter.getBuffer().toString();
        }
        catch (TransformerConfigurationException e) {
            e.printStackTrace();
        }
        catch (TransformerException e) {
            e.printStackTrace();
        }
        return null;
    }

    private Document convertToDoc(InputStream xml) {
        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();

        try {
            // Using factory get an instance of document builder
            DocumentBuilder db;

            db = dbf.newDocumentBuilder();
            // parse using builder to get DOM representation of the XML file
            Document doc = db.parse(xml);

            return doc;
        }
        catch (Exception e) {
        }

        return null;

    }
}
